// Actual render funciton

// World space coordinate
// Use right hand coordinate
//    z
//    |  y
//    |/
//    |-----x

///
// Setting scene from here
#define USE_PLANE 0
#define USE_SPHERE 1
#define USE_RECTANGLE 1
#define USE_LIGHT 0

#define DEPTH 6u // iteration for the max depth
//////////////////////////////////////////////////

///
// Plane object define
#if USE_PLANE
const uint planeCnt=1u;
SCENE_ Plane planes[]=Plane[planeCnt](
    Plane(normalize(vec3(0.,0.,1.)),-1.,DEFAULT_MAT)
);
#endif 

#if USE_RECTANGLE
const uint rectangleCnt=5u;
SCENE_ Rectangle rectangles[]=Rectangle[rectangleCnt](
    Rectangle(vec3(0, 0.3, -0.3), normalize(vec3(0,0,1)), vec2(0.22, 0.3), DEFAULT_MAT+0u),
    Rectangle(vec3(-0.22, 0.3, -0.001), normalize(vec3(1,0,0)), vec2(0.3, 0.3), DEFAULT_MAT+1u),
    Rectangle(vec3(0.22, 0.3, -0.001), normalize(vec3(-1,0,0)), vec2(0.3, 0.3), DEFAULT_MAT+3u),
    Rectangle(vec3(0, 0.3, 0.299), normalize(vec3(0,0,-1)), vec2(0.22, 0.3), DEFAULT_MAT+0u),
    Rectangle(vec3(0, 0.6, 0.0), normalize(vec3(0.001,-1,0)), vec2(.3), DEFAULT_MAT+0u)
);
#endif

/// 
// Sphere object define
#if USE_SPHERE
const uint sphereCnt=1u;
SCENE_ Sphere spheres[]=Sphere[sphereCnt](
    Sphere(.1,vec3(0.,0.4,-0.2),DEFAULT_MAT)
);
#endif

///
// World space to view space transformation
///
void WorldSpaceToViewSpace(in mat4 mv){
#if USE_PLANE
    for(uint i=0u;i<planeCnt;i++){
        vec3 n=planes[i].normal;
        mat3 v=mat3(mv[0].xyz,mv[1].xyz,mv[2].xyz);
        planes[i].normal=v*n;
    }
#endif
  
#if USE_RECTANGLE
    for(uint i=0u;i<rectangleCnt;i++){
        vec3 n=rectangles[i].normal;
        vec3 o=rectangles[i].origin;
        mat3 v=mat3(mv[0].xyz,mv[1].xyz,mv[2].xyz);
        rectangles[i].normal=v*n;
        rectangles[i].origin=(mv*vec4(o,1.)).xyz;
    }
#endif
  
#if USE_SPHERE
    for(uint i=0u;i<sphereCnt;i++){
        vec3 c=spheres[i].center;
        spheres[i].center=(mv*vec4(c,1.)).xyz;
    }
#endif

#if USE_LIGHT
    for(uint i=0u;i<lightCnt;i++){
        //TODO: different light type convert require
        vec3 d=lights[i].dir;
        mat3 v=mat3(mv[0].xyz,mv[1].xyz,mv[2].xyz);
        lights[i].dir=v*d;
    }
#endif 
}

bool WorldHit(in Ray r,out HitRecord hit_rec){
    bool hitted=false;
#if USE_PLANE
    // TODO: need modified
    for(uint i=0u;i<planeCnt;i++)
        if(IntersectWithPlane(r,planes[i],hit_rec)) hitted=true;
#endif

#if USE_RECTANGLE
    // TODO: need modified
    for(uint i=0u;i<rectangleCnt;i++)
        if(IntersectWithRectangle(r,rectangles[i],hit_rec)) hitted=true;
#endif

#if USE_SPHERE
    // TODO: need modified
    for(uint i=0u;i<sphereCnt;i++)
        if(IntersectWithSphere(r,spheres[i],hit_rec)) hitted=true;
#endif
    return hitted;
}

// trace ray according to the iterations
void RandomTrace(in Ray r,in uint light,out HitRecord hit_rec){
    // the iterations for the 
    Ray sr=r;
    for(uint i=0u;i<DEPTH;i++){
        float visibility=1.;
        // shadow ray test
#if USE_LIGHT
        if(i==1u){
            Ray shr=Ray(-lights[light].dir,vec3(hit_rec.pos));
            HitRecord shit=hit_rec;
            if(WorldHit(shr,shit)) visibility=0.;
        }
#endif
        if(WorldHit(sr,hit_rec)){
            L_existance_over_hemisphere(lights[light],hit_rec.color,sr.d,visibility,hit_rec);
            sr=Ray(
                normalize(hit_rec.normal+hemisphereSample(float(iTime+1.))),
                vec3(hit_rec.pos));
        }else{
            hit_rec.color*=mix(hit_rec.color,vec3(0.76,0.8,1.),(0.5*r.d.y+0.5));
            return;
        }
        
    }
}

// Scene intersection
void IntersectWithScene(in VS_ Ray r,in uint light,out HitRecord hit_rec){
    vec3 o=vec3(0.);
    Ray sr=r;
    bool hitted=false;
    for(uint k=2u;k>0u;k--){
#if USE_PLANE
        // TODO: need modified
        for(uint i=0u;i<planeCnt;i++)
            if(IntersectWithPlane(sr,planes[i],hit_rec)){
                if(k==2u) o=L_irradiance_over_hemisphere(lights[light],sr.d,hit_rec);
                else o*=vec3(0.5);
                // Trace 1 shadow ray
                hitted=true;
            }
#endif        
    
#if USE_SPHERE
        // TODO: need modified
        for(uint i=0u;i<sphereCnt;i++)
            if(IntersectWithSphere(sr,spheres[i],hit_rec)){
                // Test for this radiance function
                if(k==2u) o=L_irradiance_over_hemisphere(lights[light],sr.d,hit_rec);
                else o*=vec3(0.5);
                hitted=true;
            }

#endif
        if(hitted){
            sr.o=hit_rec.pos;
            sr.d=-lights[light].dir;
        }
    }
    hit_rec.color=o;
}

//
// Cast MultipleRay Single Pixel
//
void CastMultipleRay(in vec2 p,in vec2 res,out Ray[SPP] rays,in float f) {
    vec2 samples[SPP];
    vec2 uv=p/res;
    for(uint i=0u;i<SPP;i++) samples[i]=vec2(0.);    
    /// Random samples
#ifdef NON_UNIFORM
    SimpleNonUniformDisRandom(p,samples);    
#elif defined(NROOT)
    NRootSamplePP(p,samples);
#elif defined(JITTLED)
    JittledSamplePP(p,samples);
#elif defined(MULTI_JITTLED)
    MutltiJittledSamplePP(p,samples);
#endif
    // simple random ray casting
    for(uint i=0u;i<SPP;i++)
        rays[i]=CastRay(uv+vec2(1.)/res*samples[i],res,f);
}

void ProgressiveCastRay(in vec2 p,in vec2 res,out Ray ray,in float f){
    vec2 uv=p/res;
    vec2 s=vec2(0);
#ifdef NON_UNIFORM
    ProgressiveSNDR(s);
#endif
    ray=CastRay(uv+vec2(1.)/res*s,res,f);
}

void Trace(in vec2 pixelCoord,out vec3 col){    
    // Define a view info
    ViewInfo camera=CreateACamera(vec3(0.,-1.,0.),vec3(0.,10.,0.),iResolution.y,radians(45.));
    
    // NOTE:
    // It depends on the time for convergence
    // fast for compile
    // we have enough time to compute the result of multiple samples per pixel
    // so just use the simplest hash function
    ///////////////////////////////////////////////////////////////////////////
    // Some opinion:
    // may be the fastest: look-up table 
    // predefined a random number table(uniform distributed)
    // according to the time sample the table to get a random value as ray offset
    ///////////////////////////////////////////////////////////////////////////
#if PROGRESSIVE 
    Ray ray;
    ProgressiveCastRay(pixelCoord,iResolution.xy,ray,camera.f);
    WorldSpaceToViewSpace(camera.mv);
    HitRecord hit_rec=HitRecord(vec3(0.),vec3(0.),INF,vec3(1.),DEFAULT_MAT);
    // Multiple light sources
    for(uint l=0u;l<lightCnt;l++){
        //IntersectWithScene(ray,l,hit_rec);
        RandomTrace(ray,l,hit_rec);
        col+=hit_rec.color;
    }
    
#else 
    Ray rays[SPP];
    CastMultipleRay(pixelCoord,iResolution.xy,rays,camera.f);
    // Convert world space scene to camera space
    WorldSpaceToViewSpace(camera.mv);
    // Do intersection test with current scene
    for(uint i=0u;i<SPP;i++){
        // Init the hit record
        HitRecord hit_rec=HitRecord(vec3(0.),vec3(0.),INF,col,DEFAULT_MAT);
        // Multiple light sources
        for(uint l=0u;l<lightCnt;l++){
            IntersectWithScene(rays[i],l,hit_rec);
            col+=hit_rec.color;
        }
    }
    col/=vec3(SPP);
#endif
}

void mainImage(out vec4 fragColor,in vec2 fragCoord){
    /// progressive process 
    // from https://www.shadertoy.com/view/XlycWh
    if(ivec2(fragCoord)==ivec2(0)){
        fragColor=iResolution.xyxy;
    }else{
        vec3 col=vec3(0.);
        InitGlobalSeed(uvec2(fragCoord), uint(iResolution.x), uint(iFrame));
        Trace(fragCoord,col);
        if(texelFetch(iChannel0,ivec2(0),0).xy==iResolution.xy){
            fragColor=vec4(col,1)+texelFetch(iChannel0,ivec2(fragCoord),0);
        }else{
            fragColor=vec4(col,1);
        }
    }
    
}